<?php

//@todo make sure the image was loaded before and processing

/**
 * Does basic image resize and and cropping for .jpg, .png, .gif files.
 *
 * @author Dalton
 */
class EImageHelper extends CComponent {

	private $path = null, $image = null, $width = 0, $height = 0, $extension = '', $mime = '';

	function __construct ($path = null) {
		if ($path !== null)
			$this->path = $path;
		$this->load();
	}

	/**
	 * Loads the image file.
	 * @param string $path Path of the image, null to use the current one.
	 * @return mixed The current instance, false if fails.
	 */
	public function load ($path = null) {
		if (!is_null($path)) {
			$this->path = $path;
		}
		if (!is_file($this->path)) {
			throw new LogicException('Invalid path.');
		} else if (!in_array(CFileHelper::getExtension($this->path), array('jpg', 'jpeg', 'png', 'gif'))) {
			throw new LogicException(get_class($this) . ' only support .jpg, .png and .gif files.');
		}

		$info = getimagesize($this->path);
		if ($info) {
			$this->width = $info[0];
			$this->height = $info[1];
			$this->extension = substr(image_type_to_extension($info[2]), 1);
			$this->mime = $info['mime'];

			switch ($this->extension) {
				case 'jpg':
				case 'jpeg':
					$this->image = imagecreatefromjpeg($this->path);
					break;
				case 'png':
					$this->image = imagecreatefrompng($this->path);
					break;
				case 'gif':
					$this->image = imagecreatefromgif($this->path);
					break;
				default:
					throw new CException(get_class($this) . ' only support .jpg, .png and .gif files.');
					break;
			}
		} else {
			return false;
		}

		Yii::trace("Image $path loaded.", 'ext.EImageHelper');

		return $this;
	}

	/**
	 * Resizes the current image to the specified width and height.
	 * Use limit() if you don't want to stretch the image.
	 * @param int $width New width to be resized to.
	 * @param int $height New height to be resized to.
	 * @return ImageHelper An instance of this object.
	 */
	public function resize ($width, $height) {
		$img = imagecreatetruecolor($width, $height);
		imagecopyresampled($img, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
		imagedestroy($this->image);
		$this->image = $img;
		$this->width = $width;
		$this->height = $height;

		return $this;
	}

	/**
	 * Crops the image from the center. Will only crop inward, not outward.
	 * @param mixed $width New width to crop to. Use null to ignore.
	 * @param mixed $height New height to crop to. Use null to ignore.
	 * Defaults to null.
	 * @return ImageHelper An instance of this object.
	 */
	public function crop ($width, $height = null) {
		//Sets defaults, width and height cannot be bigger
		$width = is_null($width) ? $this->width : $width > $this->width ? $this->width : $width;
		$height = is_null($height) ? $this->height : $height > $this->height ? $this->height : $height;

		//To start from center
		$startX = ($this->width - $width) / 2;
		$startY = ($this->height - $height) / 2;
		$img = imagecreatetruecolor($width, $height);
		imagecopyresampled($img, $this->image, 0, 0, $startX, $startY, $width, $height, $width, $height);
		imagedestroy($this->image);

		$this->image = $img;
		$this->width = $width;
		$this->height = $height;

		return $this;
	}

	/**
	 * Resize the image ony if it exceeds the limit given.
	 * @param int $width Maximum width, use null to ignore.
	 * @param int $height Maximum height, use null to ignore.
	 * @param boolean $crop Wheter to crop out the longer side so that it fills
	 * the whole space. In other words, it would reseize by the shorter side then
	 * crop out the longer side, leaving the height and width as specified.
	 * Defaults to true.
	 * @param boolean $constraint Wheter to maintain aspect ratio. Defaults to true.
	 * @return ImageHelper An instance of this object.
	 */
	public function limit ($width, $height = null, $crop = true, $constraint = true) {
		if (is_null($width)) {
			$width = $this->width;
		}
		if (is_null($height)) {
			$height = $this->height;
		}

		if ($this->height > $height || $this->width > $width) {
			if ($crop) {
				//Resizing by width will give more height space to crop
				if ($this->findHeight($width) > $height) {
					$this->setWidth($width, $constraint);
					$this->crop($width, $height);
				} else {
					$this->setHeight($height, $constraint);
					$this->crop($width, $height);
				}
			} else {
				//Resize by the side which exceeds the wanted length
				if ($this->findHeight($width) > $height) {
					$this->height > $height && $this->setHeight($height, $constraint);
				} else {
					$this->width > $width && $this->setWidth($width, $constraint);
				}
			}
		}

		return $this;
	}

	/**
	 * Saves the loaded file to a specified location.
	 * @param mixed $path Path to save the file. Set null to override the
	 * original file. Defaults to null.
	 * @param int $quality The quality of the image to be saved as.
	 * 0 being the worst, 100 the best. Defaults to 100.
	 * @return mixed An instance of this object if successful, false otherwise.
	 */
	public function save ($path = null, $quality = 100) {
		if (is_null($path)) {
			$path = $this->path;
		}

		$saved = false;
		$ext = CFileHelper::getExtension($path);
		switch ($ext) {
			case 'jpg':
			case 'jpeg':
				if (imagetypes() & IMG_JPG) {
					$saved = imagejpeg($this->image, $path, $quality);
				}
				break;

			case 'gif':
				if (imagetypes() & IMG_GIF) {
					$saved = imagegif($this->image, $path);
				}
				break;

			case 'png':
				if (imagetypes() & IMG_PNG) {
					$quality = round(($quality / 100) * 9); //Scale quality from 0-100 to 0-9 (90%)
					$compression = 9 - $quality; //Invert quality setting as 0 is best, not 9
					imagealphablending($this->image, false);
					imagesavealpha($this->image, true);
					$saved = imagepng($this->image, $path, $compression);
				}
				break;

			default:
				return false;
				break;
		}

		Yii::trace("Save image to $path", 'ext.EImageHelper');

		return $saved ? $this : false;
	}

	/**
	 * A convenient method to delete the orginial file and save the new file to
	 * a specified location.
	 * @param string $path The new path to save the file.
	 * @param int $quality The quality of the image to be saved as.
	 * 0 being the worst, 100 the best. Defaults to 100.
	 * @return mixed An instance of this object if successful, false otherwise.
	 */
	public function transfer ($path, $quality = 100) {
		if ($this->save($path, $quality) && $this->delete($this->path)) {
			$this->path = $path;
			return $this;
		}
		return false;
	}

	/**
	 * Deletes a file, defaulting to the original image file.
	 * @param string $path The path to delete. Set null to use the current path. Defaults to null.
	 * @return mixed An instance of this object if successful, false otherwise.
	 */
	public function delete ($path = null) {
		if (is_null($path)) {
			$path = $this->path;
		}
		Yii::trace("Delete $path", 'ext.EImageHelper');
		return file_exists($path) && unlink($path) ? $this : false;
	}

	/**
	 * Get the new height based on a new width according to the image aspect ratio.
	 * @param int $width New width.
	 * @return int New height after scaling by proportion.
	 */
	public function findHeight ($width) {
		return $width / $this->width * $this->height;
	}

	/**
	 * Get the new width based on a new height according to the image aspect ratio.
	 * @param int $height New height.
	 * @return int New widht after scaling by proportion.
	 */
	public function findWidth ($height) {
		return $height / $this->height * $this->width;
	}

	public function setWidth ($width, $constraint = true) {
		if ($constraint) {
			$this->resize($width, $this->findHeight($width));
		} else {
			$this->resize($width, $this->height);
		}

		return $this;
	}

	public function setHeight ($height, $constraint = true) {
		if ($constraint) {
			$this->resize($this->findWidth($height), $height);
		} else {
			$this->resize($this->width, $height);
		}

		return $this;
	}

	public function getWidth () {
		return $this->width;
	}

	public function getHeight () {
		return $this->height;
	}

	public function getPath () {
		return $this->path;
	}

	public function getImage () {
		return $this->image;
	}

	public function getExtension () {
		return $this->extension;
	}

	public function getMime () {
		return $this->mime;
	}

}

?>
